#include <sobjc-commons/defines.h>
#include "protocol.h"

static st_table_t protocol_table;

struct protocol_iter {
  int n;
  objc_protocol_t *protocols;
};

static st_table_t get_table();
static int iter_protocol_callback(char *, objc_protocol_t, struct protocol_iter *);
static int nprotocols = 0;

static st_table_t get_table()
{
  if (!protocol_table)
    protocol_table = st_init_strtable();
  return protocol_table;
}

objc_protocol_t
objc_make_protocol(const char *name)
{
  objc_protocol_t prtc;

  prtc = ALLOC(struct objc_protocol);
  prtc->name = objc_strdup(name);
  prtc->has_def = NO;
  prtc->protocols = NULL;
  prtc->inst_meths = st_init_strtable();
  prtc->class_meths = st_init_strtable();

  return prtc;
}

objc_protocol_method_t
objc_make_protocol_method(objc_method_sig_t sig, BOOL optional, BOOL is_inst)
{
  objc_protocol_method_t meth;

  meth = ALLOC(struct objc_protocol_method);
  meth->owner = NULL;
  meth->sig = sig;
  meth->optional = optional;
  meth->is_inst = is_inst;

  return meth;
}

void
objc_register_protocol(objc_protocol_t prtc)
{
  st_table_t t;

  t = get_table();
  st_add_direct(t, (st_data_t)prtc->name, (st_data_t)prtc);
  nprotocols++;
}

void
objc_declare_protocol(const char *name)
{
  st_table_t t;
  objc_protocol_t prtc;

  t = get_table();
  if (!st_lookup(t, (st_data_t)name, (st_data_t *)&prtc)) {
    prtc = objc_make_protocol(name);
    prtc->has_def = NO;
    st_add_direct(t, (st_data_t)name, (st_data_t)prtc);
  }
}

objc_protocol_t
objc_get_protocol(const char *name)
{
  objc_protocol_t prtc;

  if (st_lookup(get_table(), (st_data_t)name, (st_data_t *)&prtc))
    return prtc;
  else
    return NULL;
}

void
objc_iter_protocols(objc_protocol_iter_f f, void *buf)
{
  struct protocol_iter it;
  int i;

  if (nprotocols <= 0)
    return;

  it.n = 0;
  it.protocols = ALLOC_N(objc_protocol_t, nprotocols);
  st_foreach(protocol_table, iter_protocol_callback, &it);
  for (i = 0; i < nprotocols; i++)
    f(it.protocols[i], buf);
}

static int
iter_protocol_callback(char *name __attribute((unused)),
                       objc_protocol_t prtc,
                       struct protocol_iter *it)
{
  it->protocols[it->n++] = prtc;

  if (it->n <= nprotocols)
    return ST_CONTINUE;
  else
    return ST_STOP;
}

void
objc_protocol_add_method(objc_protocol_t prtc,
                         objc_protocol_method_t meth,
                         BOOL is_inst)
{
  if (objc_protocol_get_method(prtc, meth->sig->name, is_inst))
    return;

  meth->owner = prtc;
  if (is_inst)
    st_add_direct(prtc->inst_meths, meth->sig->name, meth);
  else
    st_add_direct(prtc->class_meths, meth->sig->name, meth);
}

objc_protocol_method_t
objc_protocol_get_method(objc_protocol_t prtc, const char *name, BOOL is_inst)
{
  objc_protocol_method_t meth;
  objc_protocol_list_t prtcs;

  if (is_inst && st_lookup(prtc->inst_meths, name, &meth))
    return meth;
  else if (!is_inst && st_lookup(prtc->class_meths, name, &meth))
    return meth;
  else {
    prtcs = prtc->protocols;
    while (prtcs) {
      meth = objc_protocol_get_method(prtcs->car, name, is_inst);
      if (meth)
        return meth;
      else
        prtcs = prtcs->cdr;
    }
    return NULL;
  }
}

